package iceberg.rules;

import iceberg.Reference;
import iceberg.constraints.IConstraintResult;
import iceberg.constraints.WrongLayerForFunctionalityResult;
import iceberg.domain.*;
import iceberg.violations.ViolationType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WrongLayerForFunctionalityRule implements Rule {

	private static final Logger LOG = LoggerFactory.getLogger(WrongLayerForFunctionalityRule.class);

	private final StructureDefinition structureDefinition;
	private final String toPackage;
	private final Class<? extends ViolationType> violationClass;
	private final Functionality function;
	private final boolean onlyAllowedIn;

	public WrongLayerForFunctionalityRule(StructureDefinition structureDefinition, String toPackage, Class<? extends ViolationType> violationClass, Functionality function, boolean onlyAllowedIn) {
		this.structureDefinition = structureDefinition;
		this.toPackage = toPackage;
		this.violationClass = violationClass;
		this.function = function;
		this.onlyAllowedIn = onlyAllowedIn;
	}

	@Override
	public final IConstraintResult performCheck(Reference reference) {
		Layer from = structureDefinition.getWorkableLayer(reference.getFrom());
		if (from != null) {

			Functionality functionality = from.getFunctionality();
			if (suitableForChecking(functionality) && matchesFunctionalityType(functionality)) {

				String packageName = getPackageName(reference.getTo());
				if (toPackage.equals(packageName)) {
					LOG.info("Found '{}' usage in '{}'", toPackage, reference.getFrom().getName());
					try {
						return new WrongLayerForFunctionalityResult(violationClass.newInstance());
					} catch (Exception e) {
						LOG.error("An exception occurred", e);
					}
				}

			}
		}

		return null;
	}

	private boolean matchesFunctionalityType(Functionality functionality) {
		return (onlyAllowedIn && !functionality.equals(function)) || (!onlyAllowedIn && functionality.equals(function));
	}

	private boolean suitableForChecking(Functionality functionality) {
		return functionality != null && functionality != Functionality.UNDEFINED;
	}


	private String getPackageName(Unit to) {
		Unit previous = null;
		Unit next = to;
		while (to.hasParent()) {
			previous = next;
			next = next.getParent();
		}
		assert previous != null;
		return previous.getName();
	}

}
